package org.dtrd.modules.achievement.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dtrd.common.api.dto.message.MessageDTO;
import org.dtrd.common.constant.CommonConstant;
import org.dtrd.common.util.CheckUtil;
import org.dtrd.common.util.DateUtils;
import org.dtrd.common.util.StringUtil;
import org.dtrd.common.util.TimeUtil;
import org.dtrd.config.common.PathUtil;
import org.dtrd.config.system.Constant;
import org.dtrd.config.system.Constant.*;
import org.dtrd.config.wechat.api.impl.WechatApi;
import org.dtrd.config.wechat.config.WechatUtil;
import org.dtrd.config.wechat.entity.bean.AccessToken;
import org.dtrd.config.wechat.entity.bean.officialaccount.WechatTemplateParams;
import org.dtrd.modules.achievement.entity.bean.*;
import org.dtrd.modules.achievement.entity.po.DtrdEntRdSigninRecord;
import org.dtrd.modules.achievement.mapper.DtrdEntRdSigninRecordMapper;
import org.dtrd.modules.achievement.service.IDtrdEntRdSigninPlanService;
import org.dtrd.modules.achievement.service.IDtrdEntRdSigninRecordService;
import org.dtrd.modules.base.service.BaseCommonService;
import org.dtrd.modules.message.config.MessagePushConfig;
import org.dtrd.modules.message.util.MsgEventPusher;
import org.dtrd.modules.patient.entity.bean.PatientInfo;
import org.dtrd.modules.patient.entity.bean.miniprogram.PatientMiniProgramSigninStatus;
import org.dtrd.modules.patient.entity.bean.report.ReportTable;
import org.dtrd.modules.patient.entity.bean.report.ReportTableHeader;
import org.dtrd.modules.patient.service.impl.DtrdEntRdPatientServiceImpl;
import org.dtrd.modules.record.constant.SigninType;
import org.dtrd.modules.record.entity.bean.*;
import org.dtrd.modules.record.entity.bean.CheckinRecord;
import org.dtrd.modules.record.entity.bean.CheckinRecord.RecordBuilder;
import org.dtrd.modules.record.entity.po.DtrdEntRdSigninFoodRecord;
import org.dtrd.modules.record.entity.po.DtrdEntRdSigninInsulin;
import org.dtrd.modules.record.entity.po.DtrdEntRdSigninKetone;
import org.dtrd.modules.record.entity.po.DtrdViewRlCheckinRecord;
import org.dtrd.modules.record.mapper.DtrdViewRlCheckinRecordMapper;
import org.dtrd.modules.record.service.*;
import org.dtrd.modules.system.service.impl.ThirdAppWechatEnterpriseServiceImpl;
import org.dtrd.modules.util.SigninUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 萧
 * @date 2021-09-21 22:50:23
 **/
@Service
@DS("multi-datasource1")
@Slf4j
public class DtrdEntRdSigninRecordServiceImpl extends ServiceImpl<DtrdEntRdSigninRecordMapper, DtrdEntRdSigninRecord> implements IDtrdEntRdSigninRecordService {

    @Autowired
    private DtrdEntRdSigninPlanServiceImpl dtrdEntRdSigninPlanServiceImpl;

    @Autowired
    private ThirdAppWechatEnterpriseServiceImpl wechatEnterpriseService;

    @Autowired
    private DtrdEntRdSigninAchievementServiceImpl achievementServiceImpl;

    @Autowired
    @Lazy
    private WechatApi wechatApi;

    @Autowired
    private DtrdEntRdPatientServiceImpl patientServiceImpl;
    @Autowired
    private BaseCommonService baseCommonService;
    @Autowired
    private ISugarRecordService sugarRecordService;
    @Autowired
    private DtrdViewRlCheckinRecordMapper checkinRecordMapper;
    @Autowired
    private IBloodPressureRecordService bloodPressureRecordService;
    @Autowired
    private ISportRecordService sportRecordService;
    @Autowired
    private IHeightWeightRecordService heightWeightRecordService;
    @Autowired
    private IWaistlineRecordService waistlineRecordService;
    @Autowired
    private IFoodRecordService foodRecordService;
    @Autowired
    private IMedicalRecordService medicalRecordService;
    @Autowired
    private IKetoneRecordService ketoneRecordService;
    @Autowired
    private IDtrdEntRdSigninRecordService signinRecordService;
    @Autowired
    private IDtrdEntRdSigninPlanService signinPlanService;
    @Autowired
    private IInsulinRecordService insulinRecordService;
    @Autowired
    private IDtrdEntRdUplowRecordService uplowRecordService;
    @Autowired
    private INutrientRecordService nutrientRecordService;

    @Autowired
    private MsgEventPusher msgPusher;

    @Override
    public void initSignInRecord(Integer patientId) {
        SigninRecord signinRecord = getSigninRecordByPatientIdAndDate(patientId, null);
        if (signinRecord == null) {
            // 还没有打卡记录 生成新的
            generateSigninRecord(patientId, new Date());
        }
    }

    @Override
    public DtrdEntRdSigninRecord initSignInRecord(@NotNull Integer patientId, @NotNull String signinPlan, @Nullable Date recordDate) {
        // 打卡计划至少有一条打卡项时长度79 [{"recordType":5,"endtime":"14:00","isSend":0,"subType":2,"starttime":"11:00"}]
        if (CheckUtil.isNotEmpty(signinPlan) && signinPlan.length() > 60) {
            // 根据打卡计划生成打卡记录
            DtrdEntRdSigninRecord record = new DtrdEntRdSigninRecord().init(patientId);
            record.setSighInJson(signinPlan);
            if (recordDate == null) {
                recordDate = new Date();
            }
            record.setRecordDate(recordDate);
            baseMapper.insert(record);
            return record;
        }
        return null;
    }

    private DtrdEntRdSigninRecord generateSigninRecord(Integer patientId, Date recordDate) {
        SigninPlan signinPlan = dtrdEntRdSigninPlanServiceImpl.getSigninPlan(patientId, null);
        String planJson = Optional.ofNullable(signinPlan).map(BaseSigninPlan::getSignInJson).orElse(null);
        log.info("DtrdEntRdSigninRecordServiceImpl.generateSigninRecord.[patientId] patientId = " + patientId + "; signinPlan = " + signinPlan);
        return initSignInRecord(patientId, planJson, recordDate);
    }

    @Override
    public SigninRecord getSigninRecordByPatientIdAndDate(@NotNull Integer patientId, @Nullable String date) {
        if (date == null) {
            date = DateUtils.formatDate();
        }
        LambdaQueryWrapper<DtrdEntRdSigninRecord> query = new LambdaQueryWrapper<>();
        query.eq(DtrdEntRdSigninRecord::getIsDel, 0).eq(DtrdEntRdSigninRecord::getRecordDate, date).eq(DtrdEntRdSigninRecord::getPatientId, patientId);
        DtrdEntRdSigninRecord record = baseMapper.selectOne(query);
        if (record != null) {
            SigninRecord signinRecord = new SigninRecord().fromPO(record);
            List<SignInAction> actionList = signinRecord.getSignInActionList();
            actionList.sort((action1, action2) -> {
                int compare = action1.getStarttime().compareTo(action2.getStarttime());
                if (compare == 0) {
                    compare = action1.getRecordType() - action2.getRecordType();
                    if (compare == 0) {
                        compare = action1.getSubType() - action2.getSubType();
                    }
                }
                return compare;
            });
            signinRecord.setSignInActionList(actionList);
            return signinRecord;
        }
        return null;
    }

    @Override
    public List<PatientSigninRecord> getPatientSigninRecords(@NotNull Integer patientId, @Nullable String date) {
        SigninRecord sr = getSigninRecordByPatientIdAndDate(patientId, date);
        log.info("sr ==" + sr);
        if (sr != null && CheckUtil.isNotEmpty(sr.getSignInActionList())) {
            List<SignInAction> actionList = sr.getSignInActionList();
            List<PatientSigninRecord> dataList = new ArrayList<>(actionList.size());
            // 增加开始于结束时间判断，避免返回打卡计划中为空项
            actionList.stream().filter(action -> StringUtils.isNotEmpty(action.getStarttime()) && StringUtils.isNotEmpty(action.getEndtime())).map(action -> {
                PatientSigninRecord record = new PatientSigninRecord();
                record.setState(action.getIsDone());
                record.setRecordType(action.getRecordType());
                record.setDataId(action.getDataId());
                RecordType recordType = RecordType.valueOf(action.getRecordType());
                if (recordType != null) {
                    record.setTitle(recordType.getName());
                    if (recordType == RecordType.SUGAR) {
                        record.setIcon(recordType.getIcon() + action.getSubType() + ".png");
                    } else {
                        record.setIcon(recordType.getIcon());
                    }
                    // 查询对应健康记录
                    if (record.getDataId() > 0) {
                        // 查询记录详情
                        switch (recordType) {
                            case SUGAR:
                                CheckinRecord sugarCheckinRecord = sugarRecordService.selectRecordById(action.getDataId());
                                if (sugarCheckinRecord != null) {
                                    record.setValue(sugarCheckinRecord.getSugarValue() + "");
                                    record.setRecordTime(sugarCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            case BLOOD_PRESSURE:
                                CheckinRecord bpCheckinRecord = bloodPressureRecordService.selectRecordById(action.getDataId());
                                if (bpCheckinRecord != null) {
                                    record.setRecordTime(bpCheckinRecord.getRecordTimeHm());
                                    record.setValue(bpCheckinRecord.getHighPressure() + "/" + bpCheckinRecord.getLowPressure());
                                }
                                break;
                            case SPORT:
                                CheckinRecord sportCheckinRecord = sportRecordService.selectRecordById(action.getDataId());
                                if (sportCheckinRecord != null) {
                                    record.setRecordTime(sportCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            case MEDICAL:
                                CheckinRecord medicalCheckinRecord = medicalRecordService.selectRecordById(action.getDataId());
                                if (medicalCheckinRecord != null) {
                                    record.setRecordTime(medicalCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            case DIET:
                                CheckinRecord dietCheckinRecord = foodRecordService.selectRecordById(action.getDataId());
                                if (dietCheckinRecord != null) {
                                    record.setRecordTime(dietCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            case WEIGHT:
                                CheckinRecord weightCheckinRecord = heightWeightRecordService.selectRecordById(action.getDataId());
                                if (weightCheckinRecord != null) {
                                    record.setRecordTime(weightCheckinRecord.getRecordTimeHm());
                                    record.setValue(weightCheckinRecord.getWeight() + "");
                                }
                                break;
                            case WAIST:
                                CheckinRecord waistCheckinRecord = waistlineRecordService.selectRecordById(action.getDataId());
                                if (waistCheckinRecord != null) {
                                    record.setRecordTime(waistCheckinRecord.getRecordTimeHm());
                                    record.setValue(waistCheckinRecord.getWaist() + "");
                                }
                                break;
                            case KETONE:
                                CheckinRecord ketoneCheckinRecord = ketoneRecordService.selectRecordById(action.getDataId());
                                if (ketoneCheckinRecord != null) {
                                    record.setRecordTime(ketoneCheckinRecord.getRecordTimeHm());
                                    record.setValue(Optional.ofNullable(ketoneCheckinRecord.getKetone()).map(value -> value + "").orElse(null));
                                }
                                break;
                            case INSULIN:
                                CheckinRecord insulinCheckinRecord = insulinRecordService.selectRecordById(action.getDataId());
                                if (insulinCheckinRecord != null) {
                                    record.setRecordTime(insulinCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            case SLEEP:
//                                    Record sportRecord = sportRecordService.selectRecordById(action.getDataId());
                                break;
                            case NUTRIENT:
                                CheckinRecord nutrientCheckinRecord = nutrientRecordService.selectRecordById(action.getDataId());
                                if (nutrientCheckinRecord != null) {
                                    record.setRecordTime(nutrientCheckinRecord.getRecordTimeHm());
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
                if (CheckUtil.isEmpty(record.getRecordTime())) {
                    record.setRecordTime(String.format("%s-%s", action.getStarttime(), action.getEndtime()));
                }
                //
                record.setSubType(action.getSubType());
                //针对血糖 、饮食 赋值子类型
                Integer subType = action.getSubType();
                if (subType != null) {
                    switch (recordType) {
                        case SUGAR:
                            record.setRecordTypeName(Optional.ofNullable(SugarRecordType.valueOf(subType)).map(SugarRecordType::getName).orElse(""));
                            break;
                        case DIET:
                            record.setRecordTypeName(Optional.ofNullable(FoodRecordType.valueOf(subType)).map(FoodRecordType::getName).orElse(""));
                            break;
                        case INSULIN:
                            record.setRecordTypeName(Optional.ofNullable(InsulinRecordType.valueOf(subType)).map(t -> t.getName()).orElse(""));
                            break;
                        case MEDICAL:
                            record.setRecordTypeName(Optional.ofNullable(MedicalRecordType.valueOf(subType)).map(t -> t.getName()).orElse(""));
                            break;
                        case BLOOD_PRESSURE:
                            record.setRecordTypeName(Optional.ofNullable(BloodPressureRecordType.valueOf(subType)).map(t -> t.getName()).orElse(""));
                            break;
                        case NUTRIENT:
                            record.setRecordTypeName(Optional.ofNullable(NutrientRecordType.valueOf(subType)).map(t -> t.getName()).orElse(""));
                        default:
                            break;
                    }
                }
                record.setDataId(action.getDataId());
                return record;
            }).forEach(dataList::add);
            return dataList;
        }
        return null;
    }

    @Override
    public void sendRemindForAll(boolean isFirstRemind) {
        if (isFirstRemind) {
            // 当天第一次统计
            statisticsPreDayData();
        }
        List<SigninRecord> allSigninRecord = getAllSigninRecord(DateUtils.formatDate());
        baseCommonService.addLog("遍历当天所有打卡记录-" + DateUtils.formatDate() + "-记录数：" + (allSigninRecord == null ? "为空" : allSigninRecord.size()), CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
        if (allSigninRecord != null) {
            String time = DateUtils.formatShortTime();
            log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindForAll.[] allSigninRecord.size = " + allSigninRecord.size());
            allSigninRecord.forEach(record -> sendSingleRemind(record, record.getPatientId(), record.getSignInActionList(), time));
        } else {
            log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindForAll.[] allSigninRecord is empty ");
        }
    }

    private void statisticsPreDayData() {
        String date = DateUtils.dateAdd(-1);
        List<SigninRecord> allSigninRecord = getAllSigninRecord(date);
        baseCommonService.addLog("遍历前一天所有打卡记录-" + date + "-记录数：" + (allSigninRecord == null ? "为空" : allSigninRecord.size()), CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
        if (allSigninRecord != null) {
            allSigninRecord.forEach(record -> updateUnDoRecord(record, record.getPatientId(), record.getSignInActionList(), "24:00"));
        } else {
            log.info("DtrdEntRdSigninRecordServiceImpl.statisticsPreDayData.[] allSigninRecord is empty ");
        }
    }

    @Override
    public void sendRemind(Integer patientId, Integer recordType, Integer subType) {
        sendNotice(patientId, recordType, subType, null);
    }

    @Override
    public Integer getSigninFinishedAvgRate(Integer patientId, Date startDate, Date endDate) {
        Map<Date, Integer> map = getSigninFinishedRateMap(patientId, startDate, endDate);
        if (MapUtil.isNotEmpty(map)) {
            Integer count = 0;
            for (Map.Entry<Date, Integer> entry : map.entrySet()) {
                count += entry.getValue();
            }
            int avgRate = count / map.size();
            log.info("DtrdEntRdSigninRecordServiceImpl.getSigninFinishedAvgRate patientId: {}, 获取 {} ~ {} 打卡平均率: {}% ", patientId, DateUtil.formatDate(startDate), DateUtil.formatDate(endDate), avgRate);
            return avgRate;
        }
        return null;
    }

    @Override
    public Map<Date, Integer> getSigninFinishedRateMap(Integer patientId, Date startDate, Date endDate) {
        List<DtrdEntRdSigninRecord> signinRecords = listSigninRecords(patientId, startDate, endDate);
        if (CollectionUtil.isNotEmpty(signinRecords)) {
            Map<Date, Integer> map = new LinkedHashMap<>();
            for (DtrdEntRdSigninRecord signinRecord : signinRecords) {
                Integer rate = countSigninAvgRate(signinRecord);
                map.put(signinRecord.getRecordDate(), rate);
            }
            return map;
        }
        return null;
    }

    /**
     * 查询 patientId 的 startDate ～ endDate 之间的打卡记录
     */
    private List<DtrdEntRdSigninRecord> listSigninRecords(Integer patientId, Date startDate, Date endDate) {
        return list(Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).eq(DtrdEntRdSigninRecord::getPatientId, patientId).ge(DtrdEntRdSigninRecord::getRecordDate, startDate).le(DtrdEntRdSigninRecord::getRecordDate, endDate).eq(DtrdEntRdSigninRecord::getIsDel, 0).orderByAsc(DtrdEntRdSigninRecord::getRecordDate));
    }

    /**
     * 计算每个 signinRecord 的打卡率
     */
    private Integer countSigninAvgRate(DtrdEntRdSigninRecord signinRecord) {
        // region转换打卡 json 格式
        JSONArray jsonArray = JSONUtil.parseArray(signinRecord.getSighInJson());
        List<SignInAction> records = jsonArray.toList(SignInAction.class);
        if (records.size() == 0) {
            log.info("DtrdEntRdSigninRecordServiceImpl.countSigninAvgRate.[signinRecord] 打卡记录列表的格式错误, 记录id: " + signinRecord.getDataId());
            return 0;
        }
        // endregion
        // region计算打卡率
        AtomicInteger signinCount = new AtomicInteger();
        int signinSum = (int) records.stream().filter(record -> StrUtil.isNotBlank(record.getStarttime()) && StrUtil.isNotBlank(record.getEndtime())).peek(record -> {
            if (StringUtil.StrTrimInt(record.getIsDone()) == Constant.SigninDoneStatus.HAD_SIGNIN.getType()) {
                signinCount.getAndIncrement();
            }
        }).count();
        return (signinSum == 0 || signinCount.get() == 0) ? 0 : Math.min((signinCount.get() * 100) / signinSum, 100);
        // endregion
    }

    @Override
    public Integer getSigninFinishedThisWeek(Integer patientId) {
        Date now = DateUtils.getDateYMD();
        List<Date> weekDate = DateUtils.getWeekDate(now);
        Date signinPlanStartDate = dtrdEntRdSigninPlanServiceImpl.getSigninPlanStartDate(patientId);
        Date startDate = DateUtils.getLaterDay(signinPlanStartDate, weekDate.get(0));
        return getSigninFinishedAvgRate(patientId, startDate, weekDate.get(6));
    }

    @Override
    public void sendSingleRemind(SigninRecord record, Integer patientId, List<SignInAction> signInActionList, String time) {
        log.info("DtrdEntRdSigninRecordServiceImpl.sendSingleRemind.[record, patientId, signInActionList, time] patientId=" + patientId);
        if (signInActionList != null) {
            log.info("DtrdEntRdSigninRecordServiceImpl.sendSingleRemind.[record, patientId, signInActionList, time] signInActionList.size=" + signInActionList.size());
            // 找到未打卡记录
            sendRemindAndUpdate(record, patientId, signInActionList, time);
        } else {
            log.info("DtrdEntRdSigninRecordServiceImpl.sendSingleRemind.[record, patientId, signInActionList, time ]signInActionList.is emty");
        }
    }

    @Override
    @SneakyThrows
    public void saveSigninRecord(Integer dataId, Integer patientId, Integer recordType, Integer subType, String recordDate) {
        SigninRecord record = getSigninRecordByPatientIdAndDate(patientId, recordDate);
        if (record == null) {
            if (recordDate == null) {
                recordDate = DateUtils.formatDate();
            }
            Date date = DateUtils.parseDate(recordDate, DateUtils.date_sdf.get().toPattern());
            DtrdEntRdSigninRecord dtrdRecord = generateSigninRecord(patientId, date);
            if (dtrdRecord == null) {
                // 无打卡计划  没有生成打卡记录  无需继续处理
                log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId ={}, patientId= {}, recordType ={}, subType = {}  无打卡计划  没有生成打卡记录  无需继续处理 ", dataId, patientId, recordType, subType);
                return;
            }
            record = new SigninRecord().fromPO(dtrdRecord);
        }
        List<SignInAction> actionList = record.getSignInActionList();
        if (actionList != null) {
            log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId, patientId, recordType = " + recordType + ", subType=" + subType);
            actionList.stream().filter(action -> action.getRecordType().equals(recordType)).filter(action -> action.getSubType().equals(subType)).forEach(action -> {
                log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId, recordType = " + recordType + ", subType=" + subType + " ; dataId= " + dataId);
                action.setDataId(dataId);
                action.setIsDone(Constant.RecordState.RECORDED.getState());
            });
            updateSigninRecord(record);
        }
    }

    @SneakyThrows
    @Override
    public void saveSigninRecord(Integer dataId, CheckinRecord checkinRecord, Integer subType, String recordDate) {
        SigninRecord signinRecord = getSigninRecordByPatientIdAndDate(checkinRecord.getPatientId(), recordDate);
        if (signinRecord == null) {
            if (recordDate == null) {
                recordDate = DateUtils.formatDate();
            }
            Date date = DateUtils.parseDate(recordDate, DateUtils.date_sdf.get().toPattern());
            DtrdEntRdSigninRecord dtrdRecord = generateSigninRecord(checkinRecord.getPatientId(), date);
            if (dtrdRecord == null) {
                // 无打卡计划  没有生成打卡记录  无需继续处理
                log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId ={}, patientId= {}, recordType ={}, subType = {}  无打卡计划  没有生成打卡记录  无需继续处理 ", dataId, checkinRecord.getPatientId(), checkinRecord.getRecordType(), subType);
                return;
            }
            signinRecord = new SigninRecord().fromPO(dtrdRecord);
        }
        List<SignInAction> actionList = signinRecord.getSignInActionList();
        if (actionList != null) {
            log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId, patientId, recordType = " + checkinRecord.getRecordType() + ", subType=" + subType);
            actionList.stream().filter(action -> action.getRecordType().equals(checkinRecord.getRecordType())).filter(action -> action.getSubType().equals(subType)).forEach(action -> {
                log.info("DtrdEntRdSigninRecordServiceImpl.saveSigninRecord.[dataId, recordType = " + checkinRecord.getRecordType() + ", subType=" + subType + " ; dataId= " + dataId);
                action.setDataId(dataId);
                action.setRecordTime(checkinRecord.getRecordTime());
                action.setRecordPhoto(checkinRecord.getImages());
                action.setRecordValue(checkinRecord.getRecordValue());
                action.setRecordValue2(checkinRecord.getRecordValue2());
                action.setRecordValue3(checkinRecord.getRecordValue3());
                action.setRecordDesc(checkinRecord.getRecordDesc());
                action.setIsDone(Constant.RecordState.RECORDED.getState());
            });
            updateSigninRecord(signinRecord);
        }
    }

    @Override
    public void saveSigninRecord(Integer dataId, Integer subType, Function<Function<Integer, RecordBuilder>, CheckinRecord> mapper) {
        CheckinRecord checkinRecord = mapper.apply(patientId -> RecordBuilder.singleInstance().withPatientId(patientId));
        checkinRecord.setRecordDate(DateUtils.dateformat(checkinRecord.getRecordTime(), DateUtils.date_sdf.get().toPattern()));
        saveSigninRecord(dataId, checkinRecord, subType, checkinRecord.getRecordDate());
    }

    @Override
    public void summaryAchievement() {
        List<SigninRecord> allSigninRecord = getAllSigninRecord(DateUtils.formatDate());
        allSigninRecord.forEach(action -> summarySingleAchievement(action.getPatientId(), action.getRecordDate(), action.getSignInActionList()));
    }

    private void summarySingleAchievement(Integer patientId, String recordDate, List<SignInAction> signInActionList) {
        long miss = signInActionList.stream().filter(action -> action.getIsDone() == Constant.RecordState.UNRECORD.getState()).count();
        float total = signInActionList.size();
        log.info("DtrdEntRdSigninRecordServiceImpl.summarySingleAchievement.[patientId = " + patientId + " recordDate = " + recordDate + ", signInActionList miss = " + miss + " ; total=" + total);
        int score = (int) Math.ceil((1 - miss / total) * 100);
        achievementServiceImpl.saveDailyScore(patientId, recordDate, score);
    }

    @Override
    public void tagUnDoneRecord() {
        List<SigninRecord> allSigninRecord = getAllSigninRecord(DateUtils.formatDate());
        if (allSigninRecord != null) {
            String time = DateUtils.formatShortTime();
            allSigninRecord.forEach(record -> updateUnDoRecord(record, record.getPatientId(), record.getSignInActionList(), time));
        }
    }

    @Override
    public List<DailyRecord> getDailyRecordListByPatientId(Integer patientId) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> queryWrapper = Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).eq(DtrdEntRdSigninRecord::getPatientId, patientId).eq(DtrdEntRdSigninRecord::getIsDel, 0);
        List<DtrdEntRdSigninRecord> recordList = list(queryWrapper);
        // 没有查询到记录返回空列表
        if (recordList == null || recordList.isEmpty()) {
            return Collections.emptyList();
        }
        // 设置每日记录数据
        List<DailyRecord> resultList = new ArrayList<>(recordList.size());
        for (DtrdEntRdSigninRecord record : recordList) {
            DailyRecord dailyRecord = new DailyRecord();
            if (record.getRecordDate() != null) {
                dailyRecord.setRecordDate(DateUtils.formatDate(record.getRecordDate(), DateUtils.datetimeFormat.get().toPattern()));
            }
            dailyRecord.setPatientId(patientId);
            // 处理 json 记录
            String sighInJson = record.getSighInJson();
            JSONArray jsonArray = new JSONArray(sighInJson);
            List<SigninBean> signinBeans = JSONUtil.toList(jsonArray, SigninBean.class);
            for (SigninBean signinBean : signinBeans) {
                SigninType type = SigninType.getType(signinBean.getRecordType(), signinBean.getSubType());
                // 此处先假设不会有错误的类型, 即不会为 null
                assert type != null;
                switch (type) {
                    case DIET_BREAKFAST:
                        DietRecordBean breakfastRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninFoodRecord po = foodRecordService.getById(signinBean.getDataId());
                            breakfastRecord = new DietRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setBreakfastRecord(breakfastRecord);
                        break;
                    case DIET_LUNCH:
                        DietRecordBean lunchRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninFoodRecord po = foodRecordService.getById(signinBean.getDataId());
                            lunchRecord = new DietRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setLunchRecord(lunchRecord);
                        break;
                    case DIET_DINNER:
                        DietRecordBean dinnerRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninFoodRecord po = foodRecordService.getById(signinBean.getDataId());
                            dinnerRecord = new DietRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setDinnerRecord(dinnerRecord);
                        break;
                    case KETONE:
                        DailyKetoneRecordBean ketoneRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninKetone po = ketoneRecordService.getById(signinBean.getDataId());
                            ketoneRecord = new DailyKetoneRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setKetoneRecord(ketoneRecord);
                        break;
                    case INSULIN_MORNING:
                        InsulinRecordBean insulinMorningRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninInsulin po = insulinRecordService.getById(signinBean.getDataId());
                            insulinMorningRecord = new InsulinRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setInsulinRecordMorning(insulinMorningRecord);
                        break;
                    case INSULIN_NOON:
                        InsulinRecordBean insulinNoonRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninInsulin po = insulinRecordService.getById(signinBean.getDataId());
                            insulinNoonRecord = new InsulinRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setInsulinRecordNoon(insulinNoonRecord);
                        break;
                    case INSULIN_EVENING:
                        InsulinRecordBean insulinEveningRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninInsulin po = insulinRecordService.getById(signinBean.getDataId());
                            insulinEveningRecord = new InsulinRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setInsulinRecordEvening(insulinEveningRecord);
                        break;
                    case INSULIN_BEFORE_SLEEP:
                        InsulinRecordBean insulinBeforeSleepRecord = null;
                        if (signinBean.getDataId() > 0) {
                            DtrdEntRdSigninInsulin po = insulinRecordService.getById(signinBean.getDataId());
                            insulinBeforeSleepRecord = new InsulinRecordBean().parseFromPo(po);
                        }
                        dailyRecord.setInsulinRecordBeforeSleep(insulinBeforeSleepRecord);
                        break;
                }
            }
            resultList.add(dailyRecord);
        }
        return resultList;
    }

    @Override
    public PatientMiniProgramSigninStatus getPatientSignStatus(Integer patientId) {
        PatientMiniProgramSigninStatus status = new PatientMiniProgramSigninStatus();
        PatientSigninPlan signinPlan = dtrdEntRdSigninPlanServiceImpl.getSigninPlan(patientId);
        if (signinPlan == null) {
            status.setStatus(Constant.PatientSigninStatus.NO_PLAN.getType());
            return status;
        }
        Integer signInStatus = signinPlan.getSignInStatus();
        // 打卡状态判断
        if (signInStatus.equals(Constant.SignInPlanUsageStatus.Pause.getType())) {
            status.setStatus(Constant.PatientSigninStatus.PAUSE.getType());
            return status;
        } else if (signInStatus.equals(Constant.SignInPlanUsageStatus.NotStart.getType())) {
            status.setStatus(Constant.SignInPlanUsageStatus.NotStart.getType());
            return status;
        } else if (signInStatus.equals(Constant.SignInPlanUsageStatus.Finished.getType())) {
            status.setStatus(Constant.PatientSigninStatus.USING.getType());
            return status;
        }
        // 处理打卡中的状况
        status.setStatus(Constant.PatientSigninStatus.USING.getType());
        // 今天
        Date today = DateUtils.getDateYMD();
        // 本星期的 dates
        Date beginOfWeek = DateUtils.getWeekDate(today).get(0);
        // 打卡开始日期
        Date signInStartdate = signinPlan.getSignInStartdate();
        // 明天, 即查询明天之间(即今天的之内的打卡内容)
        LambdaQueryWrapper<DtrdEntRdSigninRecord> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).ge(DtrdEntRdSigninRecord::getRecordDate, signInStartdate).lt(DtrdEntRdSigninRecord::getRecordDate, today).eq(DtrdEntRdSigninRecord::getPatientId, patientId);
        List<DtrdEntRdSigninRecord> list = list(wrapper);
        // 打卡天数
        int signinDay = 0;
        // 本周漏打天数
        int noSignCountThisWeek = 0;
        // 本月漏打天数
        int noSignCountThisMonth = 0;
        for (DtrdEntRdSigninRecord record : list) {
            boolean flag = false;
            JSONArray jsonArray = JSONUtil.parseArray(record.getSighInJson());
            List<SignInAction> actions = jsonArray.toList(SignInAction.class);
            // 处理打卡 json
            for (SignInAction action : actions) {
                try {
                    // 本周的漏打次数
                    if (!action.getIsDone().equals(Constant.SigninDoneStatus.HAD_SIGNIN.getType()) && DateUtil.isIn(record.getRecordDate(), beginOfWeek, today)) {
                        noSignCountThisWeek++;
                    }
                    if (action.getIsDone().equals(Constant.SigninDoneStatus.HAD_SIGNIN.getType())) {
                        flag = true;
                    }
                } catch (Exception e) {
                    log.error("打卡json记录格式错误, 打卡id: {}", action.getDataId());
                }
            }
            // 计算打卡天数, 1天有一次打卡就算累计打卡一天
            if (flag) {
                signinDay++;
            } else {
                noSignCountThisMonth++;
            }
        }
        status.setSigninDay(signinDay);
        status.setNoSignCountThisMonth(noSignCountThisMonth);
        status.setNoSignCountThisWeek(noSignCountThisWeek);
        return status;
    }

    @Override
    public ReportTable getReportWeeklySigninRecords(Integer patientId, Date startDate, Date endDate) {
        String start = DateUtils.formatDate(startDate, DateUtils.date_sdf.get().toPattern());
        String end = DateUtils.formatDate(endDate, DateUtils.date_sdf.get().toPattern());
        List<DtrdViewRlCheckinRecord> records = checkinRecordMapper.selectCheckinRecords(patientId, start, end, null, "desc");
        if (CollectionUtil.isNotEmpty(records)) {
            ReportTable table = new ReportTable();
            // region 制作表头
            // 获取日期 -> 升序排序 -> 设置表头
            List<Date> dates = records.stream().map(DtrdViewRlCheckinRecord::getRecordDate).distinct().sorted(DateUtil::compare).collect(Collectors.toList());
            List<ReportTableHeader> headers = new ArrayList<>(dates.size() + 1);
            headers.add(new ReportTableHeader("打卡项目/时间", "k0"));
            Map<Date, String> dateToKey = new HashMap<>();
            for (int i = 0; i < dates.size(); i++) {
                String key = "k" + (i + 1);
                headers.add(new ReportTableHeader(DateUtil.formatDate(dates.get(i)), key));
                dateToKey.put(dates.get(i), key);
            }
            table.setHeader(headers);
            // endregion
            // region 设置 table 内容
            // 根据 type 进行分类
            List<cn.hutool.json.JSONObject> content = new ArrayList<>();
            // 完成率
            Map<Date, Integer> dateToRate = signinRecordService.getSigninFinishedRateMap(patientId, startDate, endDate);
            List<Date> week = DateUtils.getDateList(startDate, endDate);
            if (MapUtil.isNotEmpty(dateToRate)) {
                cn.hutool.json.JSONObject rateObj = new cn.hutool.json.JSONObject();
                rateObj.set("k0", "打卡率");
                for (int i = 0; i < week.size(); i++) {
                    Date date = week.get(i);
                    Integer rate = dateToRate.get(date);
                    String rateStr = rate == null ? "0%" : rate + "%";
                    rateObj.set("k" + (i + 1), rateStr);
                }
                content.add(rateObj);
            }
            // 打卡数据
            Map<String, List<DtrdViewRlCheckinRecord>> typeToRecords = new HashMap<>();
            for (DtrdViewRlCheckinRecord record : records) {
                String signinName = SigninUtil.getSigninName(record.getRecordType(), record.getSubType());
                List<DtrdViewRlCheckinRecord> checkinRecords = typeToRecords.get(signinName);
                if (checkinRecords == null) {
                    // 周报最多查询一周的记录
                    List<DtrdViewRlCheckinRecord> list = new ArrayList<>(7);
                    list.add(record);
                    typeToRecords.put(signinName, list);
                } else {
                    checkinRecords.add(record);
                }
            }
            for (Map.Entry<String, List<DtrdViewRlCheckinRecord>> entry : typeToRecords.entrySet()) {
                cn.hutool.json.JSONObject object = new cn.hutool.json.JSONObject();
                object.set("k0", entry.getKey());
                List<DtrdViewRlCheckinRecord> values = entry.getValue();
                for (DtrdViewRlCheckinRecord value : values) {
                    String key = dateToKey.get(value.getRecordDate());
                    Integer recordType = value.getRecordType();
                    Constant.RecordType type = Constant.RecordType.valueOf(recordType);
                    switch (type) {
                        case SUGAR:
                        case WAIST:
                        case KETONE:
                        case INSULIN:
                        case WEIGHT:
                        case SLEEP:
                        case BLOOD_PRESSURE:
                            object.set(key, value.getRecordValue());
                            break;
                        case MEDICAL:
                        case SPORT:
                        case DIET:
                            object.set(key, value.getRecordPhoto());
                            break;
                    }
                }
                content.add(object);
            }
//            table.setData(content);
            // endregion
            return table;
        }
        return null;
    }

    @Override
    public PatientSigninStatusBean getPatientIndexSignStatus(Integer patientId) {
        PatientSigninStatusBean status = new PatientSigninStatusBean();
        PatientSigninPlan signinPlan = signinPlanService.getSigninPlan(patientId);
        if (signinPlan != null) {
            status.setSigninStatus(signinPlan.getSignInStatus());
            List<DtrdEntRdSigninRecord> signinRecords = signinRecordService.list(Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).eq(DtrdEntRdSigninRecord::getPatientId, patientId).eq(DtrdEntRdSigninRecord::getIsDel, 0));
            if (CollectionUtil.isNotEmpty(signinRecords)) {
                int signinCount = 0;
                int noSigninCount = 0;
                int signinDay = 0;
                long signinCountStartTime = System.currentTimeMillis();
                for (DtrdEntRdSigninRecord signinRecord : signinRecords) {
                    JSONArray jsonArray = JSONUtil.parseArray(signinRecord.getSighInJson());
                    Object[] objects = jsonArray.toArray();
                    boolean hadSignin = false;
                    for (Object object : objects) {
                        cn.hutool.json.JSONObject jsonObject = (cn.hutool.json.JSONObject) object;
                        Integer isDone = jsonObject.getInt("isDone");
                        if (isDone != null) {
                            if (isDone == Constant.SigninDoneStatus.HAD_SIGNIN.getType()) {
                                signinCount++;
                                hadSignin = true;
                            }
                            if (isDone == Constant.SigninDoneStatus.NO_SIGNIN.getType()) {
                                noSigninCount++;
                            }
                        } else {
                            log.error("存在 isDone 为 None 的值, 打卡记录格式有问题, 记录id:{}", signinRecord.getDataId());
                        }
                    }
                    // 一天只要有一个 isDone 的记录就算已打卡
                    if (hadSignin) {
                        signinDay++;
                    }
                }
                long signinCountEndTime = System.currentTimeMillis();
                log.info("----------查询打卡天数结束，共耗时：{}----------", signinCountEndTime - signinCountStartTime);
                status.setSigninDay(signinDay);
                status.setSigninCount(signinCount);
                status.setNoSigninCount(noSigninCount);
            }
        }

        return status;
    }

    @Override
    public List<DtrdEntRdSigninRecord> listSigninRecordsByPatientIds(List<Integer> patientIds) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).in(DtrdEntRdSigninRecord::getPatientId, patientIds).orderByAsc(DtrdEntRdSigninRecord::getPatientId);
        return list(wrapper);
    }

    @Override
    public Map<Integer, Map<Integer, Integer>> getSigninRecordByPatientId(Integer patientId) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).eq(DtrdEntRdSigninRecord::getPatientId, patientId);
        List<DtrdEntRdSigninRecord> records = list(wrapper);
        if (CollectionUtil.isNotEmpty(records)) {
            HashMap<Integer, Map<Integer, Integer>> result = new HashMap<>(records.size());
            for (DtrdEntRdSigninRecord record : records) {
                try {
                    DateTime dateTime = new DateTime(record.getRecordDate());
                    int weekday = dateTime.getField(DateField.DAY_OF_WEEK);
                    Map<Integer, Integer> signinMap = result.getOrDefault(weekday, new HashMap<>(8));
                    String sighInJson = record.getSighInJson();
                    JSONArray jsonArray = JSONUtil.parseArray(sighInJson);
                    List<SignInAction> signInActions = jsonArray.toList(SignInAction.class);
                    for (SignInAction signInAction : signInActions) {
                        Integer isDone = signInAction.getIsDone();
                        Integer recordType = signInAction.getRecordType();
                        if (isDone.equals(Constant.SigninDoneStatus.HAD_SIGNIN.getType())) {
                            Integer count = signinMap.getOrDefault(recordType, 0);
                            signinMap.put(recordType, ++count);
                        }
                    }
                    result.put(weekday, signinMap);
                } catch (Exception e) {
                    log.error("打卡计划记录数据缺失, patientId: {}, 记录id: {}", patientId, record.getDataId());
                }
            }
            return result;
        }
        return null;
    }

    @Override
    public Map<Integer, Integer> getPatientSigninRecordWeekDayMap(Integer patientId) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> wrapper = Wrappers.lambdaQuery(DtrdEntRdSigninRecord.class).eq(DtrdEntRdSigninRecord::getPatientId, patientId);
        List<DtrdEntRdSigninRecord> records = list(wrapper);
        if (CollectionUtil.isNotEmpty(records)) {
            HashMap<Integer, Integer> map = new HashMap<>();
            for (DtrdEntRdSigninRecord record : records) {
                Date recordDate = record.getRecordDate();
                int dayOfWeek = DateUtil.dayOfWeek(recordDate);
                Integer count = map.getOrDefault(dayOfWeek, 0);
                map.put(dayOfWeek, ++count);
            }
            return map;
        }
        return null;
    }

    @Override
    public Map<String, String> getSigninFinishedRateWeeklyMap(int patientId, Date startDate, Date endDate) {
        Map<Date, Integer> finishedRateMap = getSigninFinishedRateMap(patientId, startDate, endDate);
        if (MapUtil.isNotEmpty(finishedRateMap)) {
            // 得到每个 weekStr 的平均率集合
            HashMap<String, List<Integer>> map = new HashMap<>();
            for (Map.Entry<Date, Integer> entry : finishedRateMap.entrySet()) {
                String week = DateUtils.getYearWeekStr(entry.getKey());
                List<Integer> list = map.getOrDefault(week, new ArrayList<>());
                list.add(entry.getValue());
                map.put(week, list);
            }
            // 将结果转换成 weekStr : 平均率 的 map
            HashMap<String, String> result = new HashMap<>();
            for (Map.Entry<String, List<Integer>> entry : map.entrySet()) {
                int sum = entry.getValue().stream().mapToInt(c -> c).sum();
                String avgStr = sum / 7 + "%";
                result.put(entry.getKey(), avgStr);
            }
            return result;
        }
        return null;
    }

    @Override
    public List<DtrdEntRdSigninRecord> getAllSigninRecords() {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> queryWrapper = baseQuery().orderByDesc(DtrdEntRdSigninRecord::getRecordDate);
        return list(queryWrapper);
    }

    private List<SigninRecord> getAllSigninRecord(String date) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> query = new LambdaQueryWrapper<>();
        query.eq(DtrdEntRdSigninRecord::getIsDel, 0);
        if (date != null) {
            query.eq(DtrdEntRdSigninRecord::getRecordDate, date);
        }
        List<DtrdEntRdSigninRecord> list = baseMapper.selectList(query);
        log.info("DtrdEntRdSigninRecordServiceImpl.getAllSigninRecord.[date = " + date + " ; list = " + (list == null ? "null" : list.size()));
        return Optional.ofNullable(list).map(l -> {
            List<SigninRecord> tmpList = new ArrayList<>(l.size());
            l.stream().map(record -> new SigninRecord().fromPO(record)).forEach(tmpList::add);
            return tmpList;
        }).orElse(null);
    }

//    @Override
//    public List<DailyGroupCheckinRecord> getSigninRecords(@Nullable Integer patientId, @Nullable String startTime, @Nullable String endTime) {
//        List<DtrdEntRdSigninRecord> list = getSigninRecords(patientId, startTime, endTime, true);
//        list.stream().filter(dr -> StrUtil.isNotBlank(dr.getSighInJson()))
//                .forEach(dr -> {
//                    List<SignInAction> actionList = JSONObject.parseArray(dr.getSighInJson(), SignInAction.class);
//                    DailyGroupCheckinRecord record = new DailyGroupCheckinRecord();
//                    record.setRecordDate(DateUtils.formatDate(dr.getRecordDate()));
//                    record.setPatientId(dr.getPatientId());
//                    ValueUplowBean sugarValueUpLowRecord = uplowRecordService.getSugarValueUpLowRecord(-1, patientId);
//                    actionList.stream()
//                            // 过滤未配置项
//                            .filter(action -> !(StringUtils.isEmpty(action.getStarttime()) || StringUtils.isEmpty(action.getEndtime())))
//                            .forEach(action -> {
//                                Constant.RecordType recordType = Constant.RecordType.valueOf(action.getRecordType());
//                                if (recordType != null) {
//                                    Integer subType = action.getSubType();
//                                    switch (recordType) {
//                                        case SUGAR:
//
//                                            break;
//                                        case BLOOD_PRESSURE:
//
//                                            break;
//                                        case SPORT:
//
//                                            break;
//                                        case MEDICAL:
//
//                                            break;
//                                        case DIET:
//
//                                            break;
//                                        case WEIGHT:
//
//                                            break;
//                                        case WAIST:
//
//                                            break;
//                                        case KETONE:
//
//                                            break;
//                                        case INSULIN:
//
//                                            break;
//                                        case SLEEP:
//
//                                            break;
//                                        default:
//                                            break;
//                                    }
//                                }
//                            });
//                });
//        List<DailyGroupCheckinRecord> dataList = null;
//        return dataList;
//    }

    @Override
    public List<DtrdEntRdSigninRecord> getSigninRecords(@Nullable Integer patientId, @Nullable String startTime, @Nullable String endTime, boolean isDateDesc) {
        LambdaQueryWrapper<DtrdEntRdSigninRecord> wrapper = baseQuery()
                .eq(patientId != null, DtrdEntRdSigninRecord::getPatientId, patientId)
                .ge(startTime != null, DtrdEntRdSigninRecord::getRecordDate, startTime)
                .le(endTime != null, DtrdEntRdSigninRecord::getRecordDate, endTime);
        if (isDateDesc) {
            wrapper.orderByDesc(DtrdEntRdSigninRecord::getRecordDate);
        } else {
            wrapper.orderByAsc(DtrdEntRdSigninRecord::getRecordDate);
        }
        return list(wrapper);
    }

    /**
     * 漏打卡消息推送
     * @param
     * @param signInActionList
     */
    @Override
    public void sendMissRecordRemind(Integer patientId, List<SignInAction> signInActionList, Integer dataId) {
        String time = DateUtils.formatShortTime();
        log.info("DtrdEntRdSigninRecordServiceImpl.sendMissRecordRemind.[patientId, signInActionList] signInActionList.size()=" + signInActionList.size());
        signInActionList.stream()
                .filter(signInAction -> matchCondition(signInAction.getIsDone(), Constant.RecordState.UNRECORD.getState()))
                .filter(signInAction -> !matchCondition(signInAction.getIsRemind(), Constant.RemindStatus.MISS_RECORD_REMINDED.getStatus()))
                .filter(signInAction -> {
            // time > endTime
            return time.compareTo(wrapperTime(signInAction.getEndtime())) > 0;
        }).forEach(signInAction -> {
            baseCommonService.addLog("漏打卡提醒并更新状态-patientId=" + patientId + "-过滤完毕,记录类型" + Constant.RecordType.valueOf(signInAction.getRecordType()).getName(), CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
            log.info("DtrdEntRdSigninRecordServiceImpl.sendMissRecordRemind.[patientId, signInActionList] ready to send QyWxMsg patientId= " + patientId + "; signInAction=" + signInAction);
            msgPusher.signinMiss(patientId, Constant.RecordType.valueOf(signInAction.getRecordType()).getName());
            signInAction.setIsRemind(Constant.RemindStatus.MISS_RECORD_REMINDED.getStatus());
        });
        SigninRecord record = new SigninRecord();
        record.setPatientId(patientId);
        record.setDataId(dataId);
        record.setSignInActionList(signInActionList);
        updateSigninRecord(record);
    }


    /**
     * 更新漏打记录状态
     *
     * @param record
     * @param patientId
     * @param signInActionList
     * @param time
     */
    private void updateUnDoRecord(SigninRecord record, Integer patientId, List<SignInAction> signInActionList, String time) {
        AtomicBoolean needUpdate = new AtomicBoolean(false);
//        log.info("DtrdEntRdSigninRecordServiceImpl.updateUnDoRecord.[record, patientId, signInActionList, time] " + signInActionList);
        signInActionList.stream().filter(signInAction -> Optional.ofNullable(signInAction.getIsDone()).orElse(Constant.RecordState.TO_BE_RECORD.getState()) == Constant.RecordState.TO_BE_RECORD.getState()).filter(signInAction -> {
            // time > endTime
            return time.compareTo(wrapperTime(signInAction.getEndtime())) > 0;
        }).forEach(signInAction -> {
            log.info("DtrdEntRdSigninRecordServiceImpl.updateUnDoRecord.[record, patientId, signInActionList, time] 匹配未打卡记录成功  置为漏打卡 signInAction=" + signInAction);
            needUpdate.set(true);
            signInAction.setIsDone(Constant.RecordState.UNRECORD.getState());
//            msgPusher.signinMiss(patientId, signInAction.getSigninItemValue());
        });
        log.info("DtrdEntRdSigninRecordServiceImpl.updateUnDoRecord.[record, patientId, signInActionList, time] after handle needUpdate=" + needUpdate + "; " + record.getDataId() + "; patientId=" + patientId);
        if (needUpdate.get()) {
            updateSigninRecord(record);
        }
    }

    /**
     * 发送提醒并更新
     *
     * @param record
     * @param patientId
     * @param signInActionList
     * @param time
     */
    private void sendRemindAndUpdate(SigninRecord record, Integer patientId, List<SignInAction> signInActionList, String time) {
//        log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindAndUpdate.[record, patientId, signInActionList, time] record=" + record);
        log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindAndUpdate.[record, patientId, signInActionList, time] signInActionList.size()=" + signInActionList.size());
        // 找到当前 尚未打卡 未提醒的记录  发送企业微信消息，并更新发送状态
        baseCommonService.addLog("准备发送提醒并更新记录状态-patientId=" + patientId + "-当前时间：" + time, CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
        signInActionList.stream()
                // 增加开关过滤条件
//                .filter(signInAction ->matchCondition(signInAction.getIsSend() , Constant.SendSwitch.SEND_OPEN.getStatus()))
                .filter(signInAction -> matchCondition(signInAction.getIsDone(), Constant.RecordState.TO_BE_RECORD.getState())).filter(signInAction -> matchCondition(signInAction.getIsRemind(), Constant.RemindStatus.TO_BE_REMIND.getStatus())).filter(signInAction -> {
//                            log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindAndUpdate.[record, patientId, signInActionList, time] signInAction.getStarttime()" + signInAction.getStarttime() + "; time=" + time + ";signInAction.getEndtime()=" + signInAction.getEndtime());
                    // startTime < time < endTime
                    return time.compareTo(wrapperTime(signInAction.getStarttime())) > 0 && time.compareTo(wrapperTime(signInAction.getEndtime())) < 0;
                }).forEach(signInAction -> {
                    baseCommonService.addLog("准备发送提醒并更新记录状态-patientId=" + patientId + "-过滤完毕,记录类型" + Constant.RecordType.valueOf(signInAction.getRecordType()).getName(), CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
                    log.info("DtrdEntRdSigninRecordServiceImpl.sendRemindAndUpdate.[record, patientId, signInActionList, time] ready to send QyWxMsg patientId= " + patientId + "; signInAction=" + signInAction);
                    sendNotice(patientId, signInAction.getRecordType(), signInAction.getSubType(), signInAction.getStarttime() + "-" + signInAction.getEndtime());
                    signInAction.setIsRemind(Constant.RemindStatus.REMINDED.getStatus());
                });
        updateSigninRecord(record);
    }

    private boolean matchCondition(Integer value, Integer conditionValue) {
        return Objects.equals(Optional.ofNullable(value).orElse(conditionValue), conditionValue);
    }

    private void sendNotice(Integer patientId, Integer recordType, Integer subType, String time) {
        // 消息接收者，传sys_user表的username字段，多个用逗号分割
//        String receiver = "Xiao,haha";
        List<Integer> idList = MessagePushConfig.getPatientIdList();
        boolean enablePush = true;
        // 未配置推送ID即允许全部推送，否则仅配置ID可以推送
        if (CollectionUtil.isNotEmpty(idList)) {
            enablePush = false;
            Optional<Integer> any = idList.stream().filter(id -> Objects.equals(id, patientId)).findAny();
            if (any.isPresent()) {
                enablePush = true;
            }
        }
        if (enablePush) {
            PatientInfo patientInfo = patientServiceImpl.selectByPatientId(patientId);
            log.info("DtrdEntRdSigninRecordServiceImpl.sendNotice.[patientId, recordType, subType, time] patientId=" + patientId);
            if (patientInfo != null && StrUtil.isNotEmpty(patientInfo.getWxOaOpenid())) {
                // 关停企微打卡提醒消息推送
//            sendQyWxMsg(patientInfo, recordType, patientId, subType, time);
                sendWxTemplateMsg(patientInfo, recordType, patientId, subType, time);
            } else {
                log.error("DtrdEntRdSigninRecordServiceImpl.sendNotice.[patientId, recordType, subType, time] patientId=" + patientId + "; 查询失败");
            }
        }
    }

    private void sendWxTemplateMsg(PatientInfo patientInfo, Integer recordType, Integer patientId, Integer subType, String time) {
        AccessToken accessToken = wechatApi.getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret());
        if (accessToken != null) {
            if (time == null) {
                time = TimeUtil.getInstance().getStandardDate(DateUtils.datetimeFormat.get().toPattern());
            }
            WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams().withFirst("您当前时段的" + Constant.RecordType.valueOf(recordType).getName() + "请按计划更新记录", "#333").withKeyword1(time).withKeyword2(Constant.RecordType.valueOf(recordType).getName() + "打卡").withKeyword4(Constant.RecordType.valueOf(recordType).getName()).withRemark("毅瑭科技智能助手提醒您，点击本条链接完成您的" + Constant.RecordType.valueOf(recordType).getName() + "打卡，如有疑问可以向您的医生和健管师进行咨询。^_^", "#333").build();
            String pagePath = "/pages/checkin/checkin?recordType=" + recordType + "&patientId=" + patientId + "&subType=" + subType + "&recordtime=" + TimeUtil.getInstance().getStandardDate(DateUtils.datetimeFormat.get().toPattern());
            wechatApi.sendPlanExecution(accessToken.getAccessToken(), patientInfo.getWxOaOpenid(), PathUtil.getH5Domain() + "/patient/index.html?userid=323", data, pagePath);
        }
    }

    private void sendQyWxMsg(PatientInfo patientInfo, Integer recordType, Integer patientId, Integer subType, String time) {
        String receiver = patientInfo.getPatientsMobile();
        log.info("DtrdEntRdSigninRecordServiceImpl.sendQyWxMsg.[patientId, recordType, subType, time] receiver=" + receiver);
        // 消息内容
//            String content = "发送" + patientId + "-" + time + "-" + recordType + "-" + subType + "-" + Constant.RecordType.valueOf(recordType).getName() + "打卡提醒-" + "http://dtrd.h5.dtx-tech.com/checkin.html?patientId=" + patientId + "&recordType=" + recordType + "&subTYpe=" + subType;
        String fromUser = "毅瑭科技";
        String title = "打卡提醒";
        String content = "毅瑭科技智能助手提醒您，点击本条链接完成您的" + Constant.RecordType.valueOf(recordType).getName() + "打卡，如有疑问可以向您的医生和健管师进行咨询。^_^";
        MessageDTO message = new MessageDTO().buildMsg(fromUser, receiver, title, content);
        message.setUrl("http://dtrd.h5.dtx-tech.com/checkin.html?patientId=" + patientId + "&recordType=" + recordType + "&subType=" + subType + "&recordTime=" + time);
        JSONObject response = wechatEnterpriseService.sendTextCardMessageResponseByPhone(message, false);
        baseCommonService.addLog("发送企业微信，patientId=" + patientId + "-当前时间：" + time + "；记录类型：" + Constant.RecordType.valueOf(recordType).getName() + "；子类型：" + subType + ";发送结果：" + response, CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_QUERY);
        log.info("DtrdEntRdSigninRecordServiceImpl.sendQyWxMsg.[patientId, recordType, subType, time] sendTextCardMessageResponseByPhone response = " + response);
    }

    /**
     * 更新打卡记录
     *
     * @param record
     */
    private void updateSigninRecord(SigninRecord record) {
//        log.info("DtrdEntRdSigninRecordServiceImpl.updateSigninRecord.[] record = " + record);
        DtrdEntRdSigninRecord entity = new DtrdEntRdSigninRecord().parseFromRecord(record);
        baseCommonService.addLog("更新打卡记录状态-patientId=" + record.getPatientId() + ";记录Id：" + record.getDataId(), CommonConstant.LOG_TYPE_OPERATION, CommonConstant.OPERATE_TYPE_UPDATE);
        saveOrUpdate(entity);
    }

    private String wrapperTime(String time) {
        if (time.length() < 5) {
            return 0 + time;
        }
        return time;
    }

    private LambdaQueryWrapper<DtrdEntRdSigninRecord> baseQuery() {
        return new LambdaQueryWrapper<>(DtrdEntRdSigninRecord.class)
                .eq(DtrdEntRdSigninRecord::getIsDel, 0);
    }

}
